package deepr.core;

import deepr.anno.*;
import deepr.exception.DeeprAssert;
import deepr.exception.DeeprException;
import deepr.lib.Map2Bean;
import deepr.lib.NameLib;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

@Slf4j
public class SchemaScanner {

  public static void scan(String path) throws Exception {
    Map<Class<?>, List<String>> hasRelationSchema = new HashMap<>();
    HashMap<Class<?>, SchemaData> relation = new HashMap<>();

    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources(path);
    for (Resource res : resources) {
      String clsName = new SimpleMetadataReaderFactory().getMetadataReader(res).getClassMetadata().getClassName();
      Class.forName(clsName).getName();
      parseClass(Class.forName(clsName), hasRelationSchema, relation);
    }

    for (Map.Entry<Class<?>, List<String>> entry : hasRelationSchema.entrySet()) {
      Class<?> czz = entry.getKey();
      List<String> keys = entry.getValue();
      SchemaData schemaData = SchemaManager.getSchemaData(czz);
      for (String key : keys) {
        PropertyData propertyData = schemaData.getPropertyData(key);
        Class<?> relationClass = propertyData.getSchema();
        SchemaData relationSchema = getRelationSchema(relation, relationClass);
        propertyData.setRelationLabel(relationSchema.getName());
        propertyData.setRelationObject(Map2Bean.firstLowerName(relationSchema.getName()));
        schemaData.putPropertyData(key, propertyData);
        schemaData.addRelationSchemaData(key, relationSchema);
      }
      SchemaManager.schemaStore.put(czz, schemaData);
    }
    SchemaManager.schemaStore.forEach((key, value) -> {
      log.debug("schema: {}, fields: {}", key, value);
    });
    SchemaManager.objectStore.forEach((key, value) -> {
      log.debug("object: {}, fields: {}", key, value);
    });
  }


  public static void parseClass(Class<?> czz, Map<Class<?>, List<String>> hasRelationSchema, HashMap<Class<?>, SchemaData> relation) throws DeeprException, NoSuchFieldException {
    //入参 要扫描的包名
    Schema schema = czz.getAnnotation(Schema.class);
    if (schema == null)
      return;
    String name = schema.name();
    Field[] fields = czz.getDeclaredFields();
    SchemaData schemaData = new SchemaData();
    HashMap<String, PropertyData> fieldMap = new HashMap<>();
    for (Field field : fields) {
      RelationProperty relationProperty = field.getAnnotation(RelationProperty.class);
      Property property = field.getAnnotation(Property.class);
      if (property == null && relationProperty == null)
        continue;
      PropertyData propertyData = new PropertyData();
      String typeStr = field.getType().toString();
      Type proType;
      String fieldName = field.getName();
      propertyData.setName(NameLib.humpToLine(fieldName));
      if (relationProperty != null) {
        propertyData.setSchema(relationProperty.schema());
        propertyData.setPoint(relationProperty.point());
        String forProperty = relationProperty.forProperty();
        if (forProperty.equals(""))
          throw new DeeprException(czz + " for " + fieldName + " not set forProperty");
        if (czz.getDeclaredField(forProperty) == null) {
          throw new DeeprException(czz + " not has property" + forProperty);
        }
        propertyData.setForProperty(relationProperty.forProperty());
        if (relationProperty.handler() != RelationProperty.class) {
          propertyData.setHandler(relationProperty.handler());
          propertyData.setHandlerArgs(relationProperty.handlerArgs());
        }
        propertyData.setRelationProperty(true);
        List<String> cur = hasRelationSchema.getOrDefault(czz, new LinkedList<>());
        cur.add(fieldName);
        hasRelationSchema.put(czz, cur);
        fieldMap.put(fieldName, propertyData);
        continue;
      }
      if (!property.name().equals(""))
        propertyData.setName(property.name());
      if (property.schema() != Schema.class) {
        propertyData.setName(fieldName);
        propertyData.setSchema(property.schema());
        if (property.forProperty().equals(""))
          throw new DeeprException(czz + " for " + fieldName + " not set forProperty");
        propertyData.setForProperty(property.forProperty());
        if (property.relation() != Schema.class) {
          propertyData.setRelation(property.relation());
        }
      }
      if (property.type() != Type.NULL) {
        proType = property.type();
      } else if (typeStr.endsWith("String")) {
        proType = Type.STRING;
      } else if (typeStr.endsWith("Integer")) {
        proType = Type.INTEGER;
      } else if (typeStr.endsWith("Double")) {
        proType = Type.DOUBLE;
      } else if (typeStr.endsWith("Long")) {
        proType = Type.LONG;
      } else if (typeStr.endsWith("Date")) {
        proType = Type.DATE;
      } else if (typeStr.endsWith("Timestamp")) {
        proType = Type.TIMESTAMP;
      } else if (typeStr.endsWith("BigDecimal")) {
        proType = Type.LONG;
      } else if (typeStr.endsWith("Float")) {
        proType = Type.FLOAT;
      } else if (typeStr.endsWith("List")) {
        proType = Type.LIST;
      } else {
        proType = Type.JSON;
      }
      propertyData.setType(proType);
      if (property.handler() != Schema.class) {
        propertyData.setHandler(property.handler());
        if (property.handlerArgs().length == 0)
          propertyData.setHandlerArgs(new String[]{fieldName});
        else
          propertyData.setHandlerArgs(property.handlerArgs());
      }
      propertyData.setIsPrimaryKey(property.primaryKey());
      if (property.primaryKey()) {
        schemaData.setPrimaryKey(fieldName);
      }
      fieldMap.put(fieldName, propertyData);
    }
    SchemaType schemaType = schema.type();

    if (schemaType != SchemaType.RELATION && schemaData.getPrimaryKey() == null) {
      throw new DeeprException(name + " not set primaryKey");
    }
    schemaData.setSchemaType(schemaType);
    schemaData.setFields(fieldMap);
    schemaData.setName(name);
    if (schema.handler() != Schema.class) {
      schemaData.setHandler(schema.handler());
    }
    schemaData.setSchema(czz);
    if (schemaType == SchemaType.RELATION) {
      relation.put(czz, schemaData);
      return;
    }
    SchemaManager.putSchema(czz, schemaData);
    if (NameLib.isAcronym(name) || name.contains("_")) {
      schemaData.setObject(name.toLowerCase());
      SchemaManager.putObject(name.toLowerCase(), czz);
      return;
    }
    if (NameLib.isLower(name)) {
      schemaData.setObject(name);
      SchemaManager.putObject(name, czz);
      return;
    }
    schemaData.setObject(NameLib.firstLowerName(name));
    SchemaManager.putObject(NameLib.firstLowerName(name), czz);
  }

  public static SchemaData getRelationSchema(HashMap<Class<?>, SchemaData> relation, Class<?> czz) {
    SchemaData schemaData = relation.get(czz);
    Assert.isTrue(schemaData != null, czz + " not exists");
    return schemaData;
  }

  public static void main(String[] args) {
    String name = "Device";
    System.out.println(NameLib.isAcronym(name));
    System.out.println(NameLib.firstAcronym(name));
  }


  public static void scan2(String path) throws Exception {
    Map<Class<?>, List<String>> hasRelationSchema = new HashMap<>();
    HashMap<Class<?>, SchemaData> relation = new HashMap<>();
    //入参 要扫描的包名
    path = path.replaceAll("\\.", "/");
    Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(path);
    while (dirs.hasMoreElements()) {
      URL url = dirs.nextElement();
      if (url.getProtocol().equals("file")) {
        scan(new File(url.getFile()), path, hasRelationSchema, relation);
      }
    }

    for (Map.Entry<Class<?>, List<String>> entry : hasRelationSchema.entrySet()) {
      Class<?> czz = entry.getKey();
      List<String> keys = entry.getValue();
      SchemaData schemaData = SchemaManager.getSchemaData(czz);
      for (String key : keys) {
        PropertyData propertyData = schemaData.getPropertyData(key);
        Class<?> relationClass = propertyData.getSchema();
        SchemaData relationSchema = getRelationSchema(relation, relationClass);
        propertyData.setRelationLabel(relationSchema.getName());
        propertyData.setRelationObject(Map2Bean.firstLowerName(relationSchema.getName()));
        schemaData.putPropertyData(key, propertyData);
        schemaData.addRelationSchemaData(key, relationSchema);
      }
      SchemaManager.schemaStore.put(czz, schemaData);
    }
    SchemaManager.schemaStore.forEach((key, value) -> {
      log.debug("schema: {}, fields: {}", key, value);
    });
    SchemaManager.objectStore.forEach((key, value) -> {
      log.debug("object: {}, fields: {}", key, value);
    });
  }


  private static void scan(File dir, String path, Map<Class<?>, List<String>> hasRelationSchema, HashMap<Class<?>, SchemaData> relation) throws Exception {
    if (dir.isDirectory()) {
      for (File f : Objects.requireNonNull(dir.listFiles())) {
        scan(f, path, hasRelationSchema, relation);
      }
      return;
    }
    if (dir.getName().endsWith(".class")) {
      loadClass(dir, path, hasRelationSchema, relation);
    }
  }

  private static void loadClass(File file, String scan, Map<Class<?>, List<String>> hasRelationSchema, HashMap<Class<?>, SchemaData> relation) throws Exception {
    // 因为scan 就是/  ， 所有把 file的 / 转成  \   统一都是：  /
    String fPath = file.getAbsolutePath().replaceAll("\\\\", "/");
    // 把 包路径 前面的 盘符等 去掉 ， 这里必须是lastIndexOf ，防止名称有重复的
    String packageName = fPath.substring(fPath.lastIndexOf(scan));
    // 去掉后缀.class ，并且把 / 替换成 .    这样就是  com.hadluo.A 格式了 ， 就可以用Class.forName加载了
    packageName = packageName.replace(".class", "").replaceAll("/", ".");
    // 根据名称加载类
    parseClass(Class.forName(packageName), hasRelationSchema, relation);
  }


}
